package com.lc1993929.dubbo.boot.service;

import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.dubbo.config.spring.ServiceBean;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.type.filter.AnnotationTypeFilter;

import java.util.Collections;
import java.util.Set;

/**
 * Created by LiuChang on 2017/9/12/012.
 * 该类会被{@link DubboScanAutoConfiguration}添加到spring的容器中
 */
public class DubboServiceBeanPostProcessor implements BeanFactoryPostProcessor, BeanPostProcessor, ApplicationContextAware {
    protected final Log logger = LogFactory.getLog(getClass());
    private ApplicationContext applicationContext;
    private final Set<String> packagesToScan;

    /**
     * {@link ApplicationContextAware}接口的方法，通过这个方法可以将获取spring的环境{@link ApplicationContext}
     *
     * @param applicationContext
     * @throws BeansException
     */
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    /**
     * 构造方法将要被扫描的包名传进来，在{@link DubboScanAutoConfiguration}的方法addPostProcessor中会在spring生成本类调用构造方法之前将要被扫描的包注入进来
     *
     * @param packagesToScan
     */
    public DubboServiceBeanPostProcessor(Set<String> packagesToScan) {
        this.packagesToScan = packagesToScan;
    }

    /**
     * 实现{@link BeanFactoryPostProcessor}接口的方法，该接口允许在容器中实例化化任何应用，所以通过该接口实例化被扫描到的类
     *
     * @param beanFactory
     * @throws BeansException
     */
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        ClassPathBeanDefinitionScanner scanner = createBeanDefinitionScanner((BeanDefinitionRegistry) beanFactory);
        for (String packageToScan : this.packagesToScan) {
            scanner.scan(packageToScan);//将被扫描到的类生成beanDefinition并注册到spring容器中
        }
    }

    /**
     * 生成针对{@link Service}的扫描器
     *
     * @param registry
     * @return
     */
    private ClassPathBeanDefinitionScanner createBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(registry);
        scanner.setEnvironment(this.applicationContext.getEnvironment());
        scanner.setResourceLoader(this.applicationContext);
        scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
        return scanner;
    }


    /**
     * 实现{@link BeanPostProcessor}接口的方法，此方法可以在spring实例化bean之前做一些操作，这里并没有进行操作
     *
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    /**
     * 实现{@link BeanPostProcessor}接口的方法，此方法可以在spring实例化bean之后做一些操作
     *
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        registerServiceBean(bean, beanName);
        return bean;
    }

    /**
     * 判断该bean是否有{@link Service}注解，如果有则将该bean在dubbo中注册并暴露
     *
     * @param bean
     * @param beanName
     * @throws BeansException
     */
    private void registerServiceBean(Object bean, String beanName) throws BeansException {
        //判断该bean是否有@Service注解
        Service service = AnnotatedElementUtils.getMergedAnnotation(bean.getClass(), Service.class);
        if (service == null) {
            return;
        }
        //如果有service注解，则将该bean在dubbo中注册并暴露
        /*ServiceBeanFactory<Object> serviceBeanFactory = new ServiceBeanFactory<Object>(service);
        serviceBeanFactory.setApplicationContext(this.applicationContext);
        serviceBeanFactory.setBeanName(beanName);
        serviceBeanFactory.setInterface(bean.getClass().getInterfaces()[0]);
        serviceBeanFactory.setRef(bean);
        try {
            serviceBeanFactory.afterPropertiesSet();//判断相关对象是否已配置
        } catch (Exception e) {
            logger.error("", e);
            throw new BeanInitializationException("", e);
        }
        serviceBeanFactory.export();//将该service在dubbo中暴露*/

        ServiceBean<Object> serviceBean = new ServiceBean<>(service);
        serviceBean.setApplicationContext(this.applicationContext);
        serviceBean.setBeanName(beanName);
        serviceBean.setInterface(bean.getClass().getInterfaces()[0]);
        serviceBean.setRef(bean);
        try {
            serviceBean.afterPropertiesSet();//判断相关对象是否已配置
        } catch (Exception e) {
            logger.error("dubbo配置service错误", e);
            throw new BeanInitializationException("", e);
        }
        serviceBean.export();
    }


    public Set<String> getPackagesToScan() {
        return Collections.unmodifiableSet(this.packagesToScan);
    }
}
